#include "Arduino.h"
#include <Esp32PcntEncoder.h>

static const char *TAG = "rotary_encoder";
#define EC11_PCNT_DEFAULT_HIGH_LIMIT (100)
#define EC11_PCNT_DEFAULT_LOW_LIMIT (-100)
#define ROTARY_CHECK(a, msg, tag, ret, ...)                                       \
	do                                                                            \
	{                                                                             \
		if (unlikely(!(a)))                                                       \
		{                                                                         \
			ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
			return false;                                                         \
		}                                                                         \
	} while (0)

//处理脉冲计数器溢出中断函数
static void pcnt_overflow_handler(void *arg)
{
    //将传入的 void *arg 强制转换为 Esp32PcntEncoder 结构体指针。
	Esp32PcntEncoder *encoder = (Esp32PcntEncoder *)arg;
    //初始化状态变量
	uint32_t status = 0;
    //获取指定 PCNT 单元的事件状态，并将结果写入 status
	pcnt_get_event_status(encoder->pcntUnit, &status);

    //检测是否触发了PCNT高限中断事件
	if (status & PCNT_EVT_H_LIM)
	{
        //累积计数器增加一个高限周期值
		encoder->accumu_count += EC11_PCNT_DEFAULT_HIGH_LIMIT;
	}

    //检测是否触发了PCNT低限中断事件
	else if (status & PCNT_EVT_L_LIM)
	{
        //累积计数器增加一个低限周期值
		encoder->accumu_count += EC11_PCNT_DEFAULT_LOW_LIMIT;
	}
}

bool Esp32PcntEncoder::init( int _pcntUnit, int pinA, int pinB)
{
    //将A/B引脚和脉冲单元传入其中
	this->_pinA = pinA;
	this->_pinB = pinB;
	this->pcntUnit = (pcnt_unit_t)_pcntUnit;

	//设置A/B引脚分别为上拉模式
	pinMode(_pinA, INPUT_PULLUP);
	pinMode(_pinB, INPUT_PULLUP);

	//定义一个 pcnt_config_t结构体变量dev_config，用于配置 PCNT 单元
	pcnt_config_t dev_config = {
        //设置脉冲输入引脚为 _pinA
		.pulse_gpio_num = (gpio_num_t)_pinA,
        // 设置控制引脚为 _pinB
		.ctrl_gpio_num = (gpio_num_t)_pinB,
        // 当控制引脚为低电平时，计数器反转计数
		.lctrl_mode = PCNT_MODE_REVERSE,
        // 当控制引脚为高电平时，计数器保持当前计数状态
		.hctrl_mode = PCNT_MODE_KEEP,
        // 当脉冲引脚检测到上升沿时，计数器递减，即_pinA
		.pos_mode = PCNT_COUNT_DEC,
        // 当脉冲引脚检测到下降沿时，计数器递增，即_pinA
		.neg_mode = PCNT_COUNT_INC,
        // 设置计数器的上限值，EC11_PCNT_DEFAULT_HIGH_LIMIT 是一个预定义的常量
		.counter_h_lim = EC11_PCNT_DEFAULT_HIGH_LIMIT,
        // 设置计数器的下限值，EC11_PCNT_DEFAULT_LOW_LIMIT 是一个预定义的常量
		.counter_l_lim = EC11_PCNT_DEFAULT_LOW_LIMIT,
        // 指定要配置的脉冲计数单元
		.unit = pcntUnit,
        // 指定使用的 PCNT 通道为通道 0
		.channel = PCNT_CHANNEL_0,
	};
    //查看是否成功配置通道
	ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 0 failed", err, ESP_FAIL);

    //设置脉冲输入引脚为 _pinB
	dev_config.pulse_gpio_num = (gpio_num_t)_pinB;
    // 设置控制引脚为 _pinA
	dev_config.ctrl_gpio_num = (gpio_num_t)_pinA;
    // 指定使用的 PCNT 通道为通道 1
	dev_config.channel = PCNT_CHANNEL_1;
    //这表示当脉冲输入引脚（这里是 _pinB）检测到上升沿时，PCNT 计数器会进行递增计数
	dev_config.pos_mode = PCNT_COUNT_INC;
    //这意味着当脉冲输入引脚（_pinB）检测到下降沿时，PCNT 计数器会进行递减计数
	dev_config.neg_mode = PCNT_COUNT_DEC;
    //查看是否成功配置通道
	ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 0 failed", err, ESP_FAIL);

	//脉冲计算器的暂停和清除
	pcnt_counter_pause(pcntUnit);
	pcnt_counter_clear(pcntUnit);

	//判断当前使用的脉冲计数单元（PCNT）是否为 PCNT_UNIT_0
	if (pcntUnit == PCNT_UNIT_0)
	{
		ROTARY_CHECK(pcnt_isr_service_install(0) == ESP_OK, "install isr service failed", err, ESP_FAIL);
	}

    //为在个脉冲单元添加一个中断系统
	pcnt_isr_handler_add(pcntUnit, pcnt_overflow_handler, this);

    //当脉冲计数单元的计数值达到预设的上限或下限时，会触发相应的中断事件，进而调用之前添加的中断处理函数
	pcnt_event_enable(pcntUnit, PCNT_EVT_H_LIM);
	pcnt_event_enable(pcntUnit, PCNT_EVT_L_LIM);

	// setGlitchFilter(1);
	start();

	return true;
}

//配置PCNT模块的输入信号滤波器，过滤短时脉冲干扰
//max_glitch_us 表示允许忽略的最大短时脉冲干扰
bool Esp32PcntEncoder::setGlitchFilter(uint32_t max_glitch_us)
{
    //检查 API 返回值是否为 ESP_OK，若失败则跳转到错误处理标签err，返回ESP_FAIL
	ROTARY_CHECK(pcnt_set_filter_value(pcntUnit, max_glitch_us * 80) == ESP_OK, //pcnt_set_filter_value:ESP-IDF提供的API，用于设置滤波器阈值
                                        "set glitch filter failed",
                                        err, 
                                        ESP_FAIL);
    
    //启用滤波器，过滤短时毛刺
	if (max_glitch_us)
	{
		pcnt_filter_enable(pcntUnit);
	}
    //禁用滤波器，允许所有脉冲通过
	else
	{
		pcnt_filter_disable(pcntUnit);
	}
	return true;
}

//启动或重置PCNT编码器
bool Esp32PcntEncoder::start()
{
    //恢复计数器运行
	pcnt_counter_resume(pcntUnit);
    //清除计数器当前值
	pcnt_counter_clear(pcntUnit);
	return true;
}

//重置编码器的计数值
bool Esp32PcntEncoder::reset()
{
    //重置为0
	_ticks = 0;
	return true;
}

//停止编码器计数
bool Esp32PcntEncoder::stop()
{
    //停止编码器计数
	pcnt_counter_pause(pcntUnit);
    //清除编码器数
	pcnt_counter_clear(pcntUnit);
	return true;
}

//获取脉冲编码器当前的值
int32_t Esp32PcntEncoder::getTicks()
{
    //初始化一个值
	int16_t val = 0;
    //读取编码器当前单元的值传入val中
	pcnt_get_counter_value(pcntUnit, &val);
    //将读取到的val和之前累加的数相加起来
	_ticks = val + accumu_count;
    //返回编码器的值
	return _ticks;
}

//通过编码器计算小车的速度
float Esp32PcntEncoder::getSpeed()
{
    //计算当前时间到上次计时之间的时间
	uint64_t dt = micros() - _lastUpdateSpeedTime;
    //当前的脉冲减去上次计算的脉冲，乘上每次脉冲移动的距离，再除以时间
	float speed = (float)(getTicks() - _lastUpdateSpeedTick) * _peerPcntDistance / dt;
    //更新脉冲数，时间
	_lastUpdateSpeedTick = getTicks();
	_lastUpdateSpeedTime = micros();
	return speed;
}

//获取每次脉冲所移动的距离
bool Esp32PcntEncoder::setPeerPcntDistance(float peerPcntDistance)
{
    //将m转化为mm
	_peerPcntDistance = peerPcntDistance * 1000 * 1000;
	return true;
}